﻿using System;
using System.Collections.Generic;
using System.Text;
using Zove.Runtime.Core;

namespace Zove.Runtime.Queries {
    public class Query {
        protected string QuerySource;

        public Query() {
            Select = new List<string>();
            Assert = new List<string>();
            Where = new Dictionary<string, string>();
        }

        public Query(string querySource)
            : this() {
            QuerySource = querySource;
        }

        public Query(string querySource, ILanguageNature languageNature)
            : this(querySource) {
            LanguageNature = languageNature;
        }

        public List<string> Select { get; set; }

        public string From { get; set; }

        public Dictionary<string, string> Where { get; set; }

        /// <summary>
        /// Asserts
        /// </summary>
        public List<string> Assert { get; set; }

        /// <summary>
        /// The instance
        /// </summary>
        public ZoveObject Instance { get; set; }

        /// <summary>
        /// The runtime model
        /// </summary>
        public RuntimeModel Model {
            get { return Instance.Model; }
        }

        /// <summary>
        /// Language nature
        /// </summary>
        public ILanguageNature LanguageNature { get; set; }

        /// <summary>
        /// Run the query
        /// </summary>
        /// <returns>the result</returns>
        public Dictionary<string, QueryResult> Run() {
            Instance = new ZoveObject(LanguageNature)
                           {
                               Name = "",
                               ClassName = From,
                               LanguageNature = LanguageNature,
                               Model = LanguageNature.ModelLoader.LoadByClassName(From)
                           };

            foreach (string s in Where.Keys) {
                Instance.Initialize(s, Where[s]);
            }

            string mathematicalExpression = string.Empty;
            QueryResult result;
            var results = new Dictionary<string, QueryResult>();
            if (Select.Count == 1 && Select[0] == "*") {
                foreach (RuntimeFactor f in Instance.Model.Factors) {
                    if (f.IsFormulatable) {
                        mathematicalExpression = f.Formulate();
                    }
                    result = new QueryResult(
                        f.Name,
                        f.Status == RuntimeFactorStatus.Unknown
                            ? "<N/A>"
                            : f.Value.ToString(), f.Unit,
                        f.Status == RuntimeFactorStatus.Input
                            ? f.Name
                            : mathematicalExpression);
                    results.Add(f.Name, result);
                }
            } else {
                if (Select.Count > 0) {
                    #region select

                    foreach (string s in Select) {
                        RuntimeFactor rf = Instance.Model.Factors[s];
                        if (rf == null) {
                            string value = Convert.ToString(Instance.Model.Evaluate(s));

                            result = new QueryResult(s, value, "", "n/a");
                        } else {
                            string message = "<n/a>";

                            if (rf.Status == RuntimeFactorStatus.Unknown) {
                                message = "<n/a>";
                            } else if(rf.IsFormulatable) {
                                mathematicalExpression = rf.Formulate();
                                message = rf.Value.ToString();
                            }

                            result = new QueryResult(
                                s, message, rf.Unit, mathematicalExpression);
                        }
                        results.Add(s, result);
                    }

                    #endregion
                } else if (Assert.Count > 0) {
                    #region Assert

                    foreach (string s in Assert) {
                        string expr = s.Split('=')[0];
                        string expected = s.Split('=')[1];

                        RuntimeFactor rf = Instance.Model.Factors[expr];
                        if (rf == null) {
                            string value = Convert.ToString(Instance.Model.Evaluate(s));

                            if (value != expected) {
                                throw new AssertException(value, expected);
                            }
                        } else {
                            if (rf.Status == RuntimeFactorStatus.Unknown) {
                                throw new AssertException("Factor {0} is not available", rf.Name);
                            }
                            if ((rf.IsFormulatable))
                            {
                                mathematicalExpression = rf.Formulate();
                            }

                            string message = rf.Value.ToString();
                            if (rf.Value.ToString() != expected) {
                                throw new AssertException(rf.Value.ToString(), expected);
                            }

                            result = new QueryResult(
                                s, message, rf.Unit, mathematicalExpression);
                            results.Add(s, result);
                        }
                    }

                    #endregion
                }
            }

            return results;
        }

        public override string ToString() {
            var b = new StringBuilder();

            if (Select.Count > 0) {
                b.Append("select ");
                foreach (string s in Select) {
                    b.AppendFormat("{0}, ", s);
                }
            } else if (Assert.Count > 0) {
                b.Append("assert ");
                foreach (string s in Assert) {
                    b.AppendFormat("{0}, ", s);
                }
            }

            b.Append("from " + From);

            b.Append("where ");
            foreach (string s in Where.Keys) {
                b.AppendFormat("{0}={1}, ", s, Where[s]);
            }

            return b.ToString();
        }
    }
}